<?php if (!defined('CORE_DIR')) die('Wrong entry point!');

/* 
 * Copyright 2010 Anton Muraviev a.k.a kuromaisu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

Aero::define('AeroRequest', AS_SINGLETON);
Aero::import('libs/uri');

/* -----------------------------------------------------------------------------
 * Class: AeroRequest
 * -----------------------------------------------------------------------------
 *
 */
class AeroRequest
{
	/*
	 * Object: uri
	 * 
	 * An instance of the <AeroUri> class initialized with the (routed) uri path.
	 * 
	 */
	public $uri;
	
	protected $method;
	protected $is_remote;
	
	protected $files;
	
	/*
	 * Constructor: __construct
	 */
	public function __construct()
	{
		// Who wants to deal with magic quotes?
		if (get_magic_quotes_gpc())
		{
		   throw new RequestException('Turn magic quotes off now!');
		}
		
		// Request method & type
		$this->method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'UKNOWN';
		$this->is_remote = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtoupper($_SERVER['HTTP_X_REQUESTED_WITH']) == 'XMLHTTPREQUEST';
		
		// Parse uri
		$script_name = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : @getenv('SCRIPT_NAME');
		$path = '';
		
		// Determine the uri path
		foreach (array('PATH_INFO','REQUEST_URI','ORIG_PATH_INFO') as $var)
		{
			$path = isset($_SERVER[$var]) ? $_SERVER[$var] : @getenv($var);

			if ($var === 'REQUEST_URI' && false !== ($pos = strpos($path, '?')))
			{
				if (count($_GET) == 0)
				{
					// Query string has not been parsed. We do it ourselves.
					parse_str(substr($path, $pos + 1), $_GET);
				}
				
				$path = substr($path, 0, $pos);
			}
			
			if ($var !== 'PATH_INFO')
			{
				$path = str_replace($script_name, '', $path);
			}

			if ($path)
			{
				break;
			}
		}
		
		// Try to rewrite the uri path
		$path = AeroUri::rewrite($path);
		
		// Create URI object
		$this->uri = Aero::load('libs/uri',$path);
	}
	
	/*
	 * Method: getMethod
	 * 
	 * Returns request method type (GET, POST, PUT, DELETE).
	 * 
	 * Returns:
	 * 	string
	 */
	public function method()
	{
		return $this->method;
	}
	
	/*
	 * Method: is
	 * 
	 * Returns TRUE if request method or type matches the string.
	 * 
	 * Parameters:
	 * 	string	-	request method (GET, POST, etc) or type (AJAX)
	 * 
	 * Returns:
	 * 	bool
	 */
	public function is($type)
	{
		$type = strtoupper($type);
		
		if ($type === 'REMOTE' || $type === 'AJAX')
		{
			return $this->is_remote;
		}
		
		return $this->method === $type;
	}
	
	/*
	 * Method: has
	 * 
	 * Determines is parameter, data or file with such name exists.
	 * 
	 * Parameters:
	 * 	string	-	type (parameter, data, file);
	 * 	string	-	name.
	 * 
	 * Returns:
	 * 	bool
	 */
	public function has($type, $name)
	{
		switch (strtolower($type))
		{
			case 'get':
			case 'parameter':
				return !empty($_GET[$name]);
			case 'post':
			case 'data':
				return !empty($_POST[$name]);
			case 'files':
			case 'file':
				if (!empty($_FILES[$name]['size']))
				{
					$names = $_FILES[$name]['name'];
					
					if (is_array($names))
					{
						foreach ($names as $name)
						{
							if (!empty($name))
							{
								return true;
							}
						}
					}
					else
					{
						return true;
					}
				}
				return false;
			default:
				return false;
		}
	}
	
	/*
	 * Method: has
	 * 
	 * Universal getter.
	 * 
	 * 
	 * 
	 * Parameters:
	 * 	string	-	type (parameter, data, file);
	 * 	string	-	name;
	 * 	misc	-	(optional) default value; does nothing for `files`.
	 * 
	 * Returns:
	 * 	misc
	 */
	public function get($type, $name, $default = false)
	{
		switch (strtolower($type))
		{
			case 'get':
			case 'parameter':
				return isset($_GET[$name]) ? $_GET[$name] : $default;
			case 'post':
			case 'data':
				return isset($_POST[$name]) ? $_POST[$name] : $default;
			case 'files':
			case 'file':
				return $this->file($name);
		}
	}
	
	/*
	 * Method: parameter
	 * 
	 * Returns value of the GET variable or $default.
	 * 
	 * Parameters:
	 * 	string	-	variable name
	 * 	misc	-	default value
	 * 
	 * Returns:
	 * 	Misc
	 */
	public function parameter($name, $default = false, $constraints = '', $context = null)
	{
		$value = isset($_GET[$name]) ? $_GET[$name] : $default;
		
		if (strlen($constraints) > 0)
		{
			$this->_validate('parameter',$name, $value, $constraints, $context);
		}
		
		return $value;
	}
	
	/*
	 * Method: data
	 * 
	 * Returns value of the POST variable or $default.
	 * 
	 * Parameters:
	 * 	string	-	variable name
	 * 	misc	-	default value
	 * 
	 * Returns:
	 * 	Misc
	 */
	public function data($name, $default = false, $constraints = '', $context = null)
	{
		$value = isset($_POST[$name]) ? $_POST[$name] : $default;
		
		if (strlen($constraints) > 0)
		{
			$this->_validate('data',$name, $value, $constraints, $context);
		}
		
		return $value;
	}
	
	/*
	 * Method: files
	 * 
	 * Returns an array of uploaded files. Each item (an associative array of 
	 * parameters) in the array corresponds to one file.
	 * 
	 * File parameters:
	 * 	name		-	original file name (example.txt)
	 * 	type		-	mime type (text/plain)
	 * 	size		-	file size in bytes
	 * 	tmp_name	-	path to temporary copy of the file
	 * 					(/var/uploads/php0324d); use move_uploaded_file()
	 * 					to copy file to permament location
	 * 	error		-	an error code (in case there was an error);
	 * 					UPLOAD_ERR_OK or 0 means "no error"
	 * 
	 * Parameters:
	 * 	string	-	collection name (name of the file input)
	 * 
	 * Returns:
	 * 	Array
	 */
	public function files($collection)
	{
		if (!is_array($this->files) && is_array($_FILES) && count($_FILES) > 0)
		{
			foreach ($_FILES as $name => $data)
			{
				$this->files[$name] = array();
				
				foreach ($data as $param => $value)
				{
					if (!is_array($value))
					{
						$value = array($value);
					}
					
					foreach ($value as $key => $_value)
					{
						if ($param === 'tmp_name' && !is_uploaded_file($_value))
						{
							unset($this->files[$name][$key]);
							continue 2;
						}
						
						$this->files[$name][$key][$param] = $_value;
					}
				}
			}
		}
		else
		{
			$this->files = array();
		}
		
		return isset($this->files[$collection]) ? $this->files[$collection] : array();
	}
	
	/*
	 * Method: _validate
	 *
	 * A helper method for data() and parameter() validation.
	 *
	 */
	protected function _validate($what, $name, $value, $constraints, $context)
	{
		$validator = Aero::load('libs/validator');
		
		if (true !== ($error = $validator->run($value, $constraints, $context)))
		{
			array_shift($error['arguments']);
			throw new RequestException(ucfirst($what).' validation failed. '.ucfirst($what).': "'.$name.'". Rule: '.$error['rule'].(count($error['arguments']) > 0 ? '['.implode(', ',$error['arguments']).']' : '').'.');
		}
		
		return $value;
	}
}

/* -----------------------------------------------------------------------------
 * Class: RequestException
 * -----------------------------------------------------------------------------
 */
class RequestException extends Exception {}
